/**
 * 
 */
package cz.cuni.mff.abacs.burglar.logics.planning;

import cz.cuni.mff.abacs.burglar.logics.GameMap;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Guard;
import cz.cuni.mff.abacs.burglar.logics.planning.instructions.Instruction;
import cz.cuni.mff.abacs.burglar.visual.VisualBurglar;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


/** 
 * Handler of the external planner tool.
 * 
 * @author abacs
 *
 */
public class SGPLANHandler extends PlannerHandlerBase implements PlannerHandler {
	
	
	/** '/' terminated path to the planner implementation directory. */
	private static String SUBPATH_PLANNER = "sgplan/";
	
	/** Planner executable name. */
	private static String PLANNER_NAME = "sgplan";
	
	/** Planning time limit in seconds. */
	private static int PLANNING_TIME_LIMIT = 8;
	
	// -------------------------------------------------------------------------
	
	
	@Override
	public List<Instruction> solveBurglarProblem(
			GameMap map,
			List<Integer> roomsToEnter,
			List<Integer> roomsToAvoid
	) {
		try{
			// get the burglar solution:
			BufferedReader input = 
				SGPLANHandler.solveBurglarProblem_(map, roomsToEnter, roomsToAvoid);
			
			List<Instruction> instructions = new ArrayList<Instruction>();
			SGPLANHandler.translateInstructions(input, map, instructions);	
			
			return instructions;
		}catch(Exception e){
			System.err.println(e.toString());
			e.printStackTrace();
		}
		return new ArrayList<Instruction>();
	}
	
	
	@Override
	public List<Instruction> solveGuardProblem(
			Guard guard,
			GameMap map,
			List<Integer> roomsToEnter,
			List<Integer> roomsToAvoid
	) {
		try{
			// get the burglar solution:
			BufferedReader input = 
				SGPLANHandler.solveGuardProblem_(guard, map, roomsToEnter, roomsToAvoid);
			
			List<Instruction> instructions = new ArrayList<Instruction>();
			SGPLANHandler.translateInstructions(input, map, instructions);
			
			return instructions;
		}catch(Exception e){
			System.err.println(e.toString());
			e.printStackTrace();
		}
		return new ArrayList<Instruction>();
	}
	
	
	// -------------------------------------------------------------------------
	
	
	private static BufferedReader solveBurglarProblem_(
			GameMap map,
			List<Integer> roomsToEnter,
			List<Integer> roomsToAvoid
	) {
		String domain = VisualBurglar.PATH_PLANNING + SUBPATH_DOMAINS + "agent.pddl";
		String problem = VisualBurglar.PATH_PLANNING + SUBPATH_PROBLEMS + "burglar.pddl";
		String program = VisualBurglar.PATH_PLANNING + SUBPATH_PLANNER + PLANNER_NAME;
		
		PDDLGenerator.generateBurglarProblem(
				map,
				roomsToEnter,
				roomsToAvoid,
				problem
		);
		
		BufferedReader input = null;
		try{
			Runtime runtime = Runtime.getRuntime();
			
			Process process = 
				runtime.exec(
					program +
					" -o " + domain +
					" -f " + problem +
					" -cputime " + PLANNING_TIME_LIMIT
				);
			
			if(process.waitFor() == EXIT_VALUE_SUCCESS)
				input = 
					new BufferedReader(new InputStreamReader(process.getInputStream()));
			else
				input = null;
		}catch(Exception e){
			System.err.println(e.toString());
			e.printStackTrace();
		}
		return input;
	}
	
	
	private static BufferedReader solveGuardProblem_(
			Guard guard,
			GameMap map,
			List<Integer> roomsToEnter,
			List<Integer> roomsToAvoid
	) {
		
		String domain = VisualBurglar.PATH_PLANNING + SUBPATH_DOMAINS + "agent.pddl";
		String problem = VisualBurglar.PATH_PLANNING + SUBPATH_PROBLEMS + "guard.pddl";
		String program = VisualBurglar.PATH_PLANNING + SUBPATH_PLANNER + PLANNER_NAME;
		
		PDDLGenerator.generateGuardProblem(
				guard,
				map,
				roomsToEnter,
				roomsToAvoid,
				problem
		);
		
		BufferedReader input = null;
		try{
			Runtime runtime = Runtime.getRuntime();
						
			Process process = 
				runtime.exec(
					program +
					" -o " + domain +
					" -f " + problem +
					" -cputime " + PLANNING_TIME_LIMIT
				);
			
			if(process.waitFor() == EXIT_VALUE_SUCCESS)
				input = 
					new BufferedReader(new InputStreamReader(process.getInputStream()));
			else
				input = null;
		}catch(Exception e){
			System.err.println(e.toString());
			e.printStackTrace();
		}
		return input;
	}
	
	
	// -------------------------------------------------------------------------
	
	
	private static List<String> filterLines(BufferedReader input) throws IOException {
		String regex = "^\\d+\\.\\d+\\: \\(.*";
		
		List<String> lines = new LinkedList<String>();
		String currentLine;
		
		while((currentLine = input.readLine()) != null){
			if(currentLine.matches(regex))
				lines.add(currentLine);
		}
		
		return lines;
	}
	
	
	/**
	 * Goes though the input string line by line and translates 
	 * the instructions from planner specific string format.
	 * 
	 * @param input file input
	 * @param map 
	 * @param instructions
	 * @throws IOException 
	 */
	private static void translateInstructions(
			BufferedReader input,
			GameMap map,
			List<Instruction> instructions
	) throws IOException {
		if(input == null){
			return;
		}
		
		// filter lines:
		List<String> lines = SGPLANHandler.filterLines(input);
		
		for(String line : lines){
			SGPLANHandler.translateInstruction(line, map, instructions);
		}
	}
	
	
	/**
	 * Translates a single instruction.
	 * 
	 * @param planLine line to translate
	 * @param map 
	 * @param instructions
	 * @return true if agent just entered a new room.
	 */
	private static boolean translateInstruction(
			String planLine,
			GameMap map,
			List<Instruction> instructions
	) {
		// remove the unnecessary chars:
		planLine = planLine.trim();
		planLine = planLine.replace("(", "");
		planLine = planLine.replace(")", "");
		
		// split into words:
		String[] words = planLine.split(" ");
		
		// translate the instruction code:
		Instruction.code code = SGPLANHandler.getInstructionCode(words[1]);
		
		// get the agent:
		int agentId = -1;
		try{
			agentId = Integer.parseInt(words[2].substring(1));
		}catch(NumberFormatException e){ 
			System.err.println(e.toString());
			e.printStackTrace();
		}
		
		int positionId;
		
		switch(code){
		case COMPLEX_MOVE:
			int moveToId = Integer.parseInt(words[4].substring(1));
			
			instructions.add(new Instruction(code, agentId, moveToId));
			return false;
		case ENTER_DOOR:
			int doorId = Integer.parseInt(words[3].substring(1));
			
			instructions.add(new Instruction(code, agentId, doorId));
			return true;
		case OPEN:
		case CLOSE:
		case USE:
			positionId = Integer.parseInt(words[3].substring(1));
			
			instructions.add(new Instruction(code, agentId, positionId));
			return false;
		case UNLOCK:
		case LOCK:
		case PICK_UP:
			positionId = Integer.parseInt(words[3].substring(1));
			int itemId = Integer.parseInt(words[4].substring(1));
			
			instructions.add(
					new Instruction(code, agentId, positionId, itemId)
			);
			return false;
		case TAKE_CLOTHES:
			positionId = Integer.parseInt(words[3].substring(1));
			
			instructions.add(
					new Instruction(code, agentId, positionId)
			);
			return false;
		default:
			
		}
		return false;
	}
	
}
